﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;
using System.Threading;
using System.Runtime.InteropServices;

namespace CleanXMLFile
{
    class Program
    {
        private static bool[] _lookup;
        private static string path = String.Empty;
        private static string searchFilter = "*.*";
        private static int numberOfThreads = 1;
        private static string logFilePath = String.Empty;


        static void Main(string[] args)
        {
            if (ParseInputArguments(args))
            { 
                PrintMessage("Program started.");                
                DateTime timeStarted = DateTime.Now;
                PrintMessage(String.Format("Time started: {0} : {1}", DateTime.Now.ToShortDateString(), DateTime.Now.ToShortTimeString()));
                //Create UNC path from normal file path. To handle paths longer than 260 characters
                string uncPath = "\\\\localhost\\" + path.Replace(':','$');                        
                   
                PrintMessage(String.Format("Cleaning {0} in {1}.\n",searchFilter,path));

                //DirectoryInfo info = new DirectoryInfo(path);
                String[] files = null;            
                int numberOfFiles = 0;
                ManualResetEvent resetEvent = new ManualResetEvent(false);
                Worker w = new Worker();
                w.resetEvent = resetEvent;

                PrintMessage("Initializing caracter lookup table...");
                InitalizeLookup();
                PrintMessage("Initialization complete...");
                PrintMessage(String.Format("Enumerating \"{0}\" files...", searchFilter));

                try
                {                
                    files = Directory.GetFiles(uncPath, searchFilter, SearchOption.AllDirectories);                    
                    numberOfFiles = files.Length;
                    PrintMessage(String.Format("Found {0} files.", numberOfFiles));
                }
                catch (Exception ex)
                {
                    PrintMessage(String.Format("Exception: {0}", ex.Message));
                }

                if (files != null && files.Count() > 0)
                {
                    int filesOnAThread = (int)Math.Floor(( numberOfFiles / (decimal)numberOfThreads));
                    int filesLeft = numberOfFiles;

                    for (int i = 1; i <= numberOfThreads; i++)
                    {
                        String[] batch;

                        if (i == numberOfThreads)
                        {
                            PrintMessage(String.Format("Geting file segment for a thread. Files on a thread={0}", filesLeft));
                            batch = GetSequenceOfFiles(numberOfFiles - filesLeft, filesLeft, files);
                            PrintMessage(String.Format("Starting thread {0}.", i));
                        }
                        else
                        {
                            PrintMessage(String.Format("Geting file segment for a thread. Files on a thread={0}", filesOnAThread));
                            batch = GetSequenceOfFiles(numberOfFiles - filesLeft, filesOnAThread, files);
                            PrintMessage(String.Format("Starting thread {0}.", i));
                        }

                        WorkerArguments  state = new WorkerArguments();
                        state.batch = batch;
                        state.LookupTable = _lookup;
                        filesLeft -= filesOnAThread;                    
                        ThreadPool.QueueUserWorkItem(new WaitCallback(w.CleanXML), state);     
                    }                
                }

                resetEvent.WaitOne();
                PrintMessage(String.Format("Time finished: {0} : {1}", DateTime.Now.ToShortDateString(), DateTime.Now.ToShortTimeString()));
                PrintMessage(String.Format("Total time elapsed: {0} seconds.", (DateTime.Now - timeStarted).TotalSeconds));
                PrintMessage(String.Format("Finished. Pres Enter to exit."));
                Console.ReadLine();
            }
        }

        private static String[] GetSequenceOfFiles(int startIndex, int length, String[] files)
        {
            String[] batch = new String[length];

            for (int i = startIndex, j=0; i < startIndex + length; ++i, ++j )
            {
                batch[j] = files[i];
            }

            return batch;
        }

        //Tablica legalnih znakova
        private static void InitalizeLookup()
        {
            _lookup = new bool[65535];

            _lookup[0x9] = true;
            _lookup[0xA] = true;
            _lookup[0xD] = true;
            
            for (char c = (char)0x20; c <= 0xD7FF; c++) _lookup[c] = true;
            for (char c = (char)0xE000; c <= 0xFFFD; c++) _lookup[c] = true;
            for (char c = (char)0x1000; c <= 0x10FF; c++) _lookup[c] = true;                 
        }

        private static bool ParseInputArguments (string[] args)
        {
            if(args.Length == 2 &&  ((string)args[0] == "-H" || (string)args[0] == "-h" ))
            {
                PrintHelp();
                return false;
            }
            else if (args.Length > 2 && args.Length < 6)
            {
                PrintMessage("Not enough arguments passed!");
                PrintHelp();
                return false;
            }
            else
            {
                for (int i = 0; i < args.Length; i+=2)
                {
                    string option = args[i];
                    switch(option.ToUpper())
                    {
                        case "-P":
                            path = args[i+1];  
                            break;                       
                        case "-F":
                            searchFilter = args[i+1];
                            break;                        
                        case "-T":                               
                            numberOfThreads = Int32.Parse(args[i+1]);   
                            break;                        
                        case "-L":
                            logFilePath = args[i+1];
                            break;
                        default:
                            break;
                    }
                }

                if (String.IsNullOrEmpty(path)) return false;

                return true;
            }

        }

        private static void PrintHelp()
        {
            Console.WriteLine("Program usage:");
            Console.WriteLine("\t-p Directory path to check (recursive)");
            Console.WriteLine("\t-f File filter (like *.xml)");
            Console.WriteLine("\t-t Number of threads (max 64)");
            Console.WriteLine("\t-l Path for log file");
            Console.WriteLine();
        }

        private static void PrintMessage(string message)
        {
            Console.WriteLine(message);

            if (!String.IsNullOrEmpty(logFilePath))
            {
                FileStream fs = new FileStream(logFilePath, FileMode.Append, FileAccess.Write);
                StreamWriter writer = new StreamWriter(fs);
                writer.WriteLine(DateTime.Now.ToShortDateString() + "::" + DateTime.Now.ToShortTimeString() + "\t" + message);
                writer.Flush();
                fs.Close();
            }

        }
    }
}
